Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Last updated: Thursday 17 September 2026 @ 16:50:33
(async () => {
  "use strict";

  // ============================================================
  // Codex Quota Compass V2.3
  // 多模型费率反校正版 · 纯实测 · 无套餐倍率
  //
  // 数据来源:
  //
  // 1. /backend-api/wham/usage
  //    -> 实时 weekly used_percent / reset
  //
  // 2. /backend-api/wham/usage/daily-token-usage-breakdown
  //    -> 每天每个模型消耗的 allowance 百分点
  //
  // 3. /backend-api/wham/analytics/daily-workspace-usage-counts
  //    -> 每日总 uncached / cached / output token
  //    -> 每模型 turns(只展示,不用于价格分配)
  //
  // 个人账号没有 per-model token breakdown。
  //
  // 多模型日算法:
  //
  //   当天 Token 结构:
  //     fu = uncached / total
  //     fc = cached   / total
  //     fo = output   / total
  //
  //   模型综合费率:
  //     Rm = fu * inputRate
  //        + fc * cachedRate
  //        + fo * outputRate
  //
  //   OpenAI 给出的该模型 allowance 百分点 = Pm
  //
  //   Token 权重:
  //     Wm = Pm / Rm
  //
  //   模型 Token share:
  //     Sm = Wm / sum(W)
  //
  // 再按 Sm 分配当天三类总 Token。
  //
  // 这样可以保证昂贵模型不会因为 quota 占比高
  // 就被错误分配同样比例的 Token。
  //
  // 注意:
  // - 完全没有 Plus×1 / ProLite×5 / Pro×20
  // - plan_type 只显示
  // - 忽略 >272K long-context multiplier
  // - 所有价格始终使用基础 Token 单价
  // ============================================================

  const CONFIG = {
    HISTORY_DAYS: 45,

    USD_PER_CREDIT: 0.04,

    // allowance 百分比太小的日子容易受统计舍入影响。
    MIN_DAY_PERCENT: 0.10,

    // 同一周期多个反推结果的允许离散。
    CLUSTER_TOLERANCE: 0.10,

    // ==========================================================
    // OpenAI 基础 API / ChatGPT Work Token rate card
    // USD / 1M tokens
    //
    // 按用户要求:
    // 不考虑 >272K context 的 2x / 1.5x 加价。
    // ==========================================================

    MODEL_RATES: {
      "gpt-6-astra": {
        input: 10.00,
        cached: 1.00,
        output: 50.00,
      },

      "gpt-5.6-sol": {
        input: 4.00,
        cached: 0.40,
        output: 20.00,
      },

      "gpt-5.6-terra": {
        input: 2.00,
        cached: 0.20,
        output: 12.00,
      },

      "gpt-5.6-luna": {
        input: 0.20,
        cached: 0.02,
        output: 1.20,
      },

      "gpt-5.5": {
        input: 5.00,
        cached: 0.50,
        output: 30.00,
      },

      "gpt-5.4": {
        input: 2.50,
        cached: 0.25,
        output: 15.00,
      },

      "gpt-5.4-mini": {
        input: 0.75,
        cached: 0.075,
        output: 4.50,
      },

      "gpt-5.3-codex": {
        input: 1.75,
        cached: 0.175,
        output: 14.00,
      },

      "gpt-5.2": {
        input: 1.75,
        cached: 0.175,
        output: 14.00,
      },

      "daybreak-blue": {
        input: 4.00,
        cached: 0.40,
        output: 20.00,
      },

      "daybreak-red": {
        input: 12.50,
        cached: 1.25,
        output: 75.00,
      },
    },
  };

  // ============================================================
  // 基础工具
  // ============================================================

  const n = (v) => {
    const x = Number(v);
    return Number.isFinite(x) ? x : 0;
  };

  const clamp = (v, min, max) =>
    Math.min(max, Math.max(min, v));

  const fmtMoney = (v) =>
    Number.isFinite(v)
      ? `$${v.toFixed(2)}`
      : "—";

  const fmtPct = (v, d = 3) =>
    Number.isFinite(v)
      ? `${v.toFixed(d)}%`
      : "—";

  const fmtNum = (v) => {
    if (!Number.isFinite(v)) return "—";

    if (v >= 1e9)
      return `${(v / 1e9).toFixed(2)}B`;

    if (v >= 1e6)
      return `${(v / 1e6).toFixed(2)}M`;

    if (v >= 1e3)
      return `${(v / 1e3).toFixed(2)}K`;

    return Math.round(v).toLocaleString();
  };

  const dayKey = (ms) =>
    new Date(ms).toISOString().slice(0, 10);

  const addDays = (key, days) => {
    const d = new Date(`${key}T00:00:00Z`);
    d.setUTCDate(d.getUTCDate() + days);
    return d.toISOString().slice(0, 10);
  };

  function median(values) {
    const a = values
      .filter(Number.isFinite)
      .sort((x, y) => x - y);

    if (!a.length) return null;

    const i = Math.floor(a.length / 2);

    return a.length % 2
      ? a[i]
      : (a[i - 1] + a[i]) / 2;
  }

  // ============================================================
  // Auth
  // ============================================================

  function getAccessToken() {
    const boot =
      document.getElementById("client-bootstrap")
        ?.textContent || "";

    return (
      boot.match(
        /[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/
      )?.[0] || null
    );
  }

  async function apiGet(path, token) {
    const res = await fetch(path, {
      credentials: "include",

      headers: {
        Authorization: `Bearer ${token}`,
        Accept: "application/json",
      },
    });

    const text = await res.text();

    if (!res.ok) {
      throw new Error(
        `HTTP ${res.status}: ${text.slice(0, 300)}`
      );
    }

    return JSON.parse(text);
  }

  // ============================================================
  // Model helpers
  // ============================================================

  function normalizeModel(model) {
    return String(model || "")
      .trim()
      .toLowerCase();
  }

  function getRate(model, speed = "standard") {
    // 这里故意不把 Fast 套用 Standard 价格。
    // 当前脚本只精确处理 standard。
    if (speed && speed !== "standard") {
      return null;
    }

    return (
      CONFIG.MODEL_RATES[
        normalizeModel(model)
      ] || null
    );
  }

  function getModelTurns(countRow) {
    const result = new Map();

    for (const m of countRow?.models || []) {
      const model = normalizeModel(m.model);

      result.set(
        model,
        (result.get(model) || 0) +
          n(m.turns)
      );
    }

    return result;
  }

  // ============================================================
  // 多模型反向分配
  // ============================================================

  function estimateDay(countRow, pctRow) {
    const totals = countRow?.totals;

    if (!totals) {
      return {
        valid: false,
        reason: "Token 尚未结算",
      };
    }

    const uncached =
      n(totals.uncached_text_input_tokens);

    const cached =
      n(totals.cached_text_input_tokens);

    const output =
      n(totals.text_output_tokens);

    const total =
      uncached + cached + output;

    if (!(total > 0)) {
      return {
        valid: false,
        reason: "无 Token",
      };
    }

    // 当天 Token 类型结构。
    const fu = uncached / total;
    const fc = cached / total;
    const fo = output / total;

    const modelRows = (pctRow?.models || [])
      .map((m) => ({
        model: normalizeModel(m.model),
        speed: m.speed || "standard",
        percent: n(m.credits),
      }))
      .filter((m) => m.percent > 0);

    const totalPercent =
      modelRows.reduce(
        (sum, m) => sum + m.percent,
        0
      );

    if (!(totalPercent > 0)) {
      return {
        valid: false,
        reason: "无 allowance 百分比",
      };
    }

    if (
      totalPercent <
      CONFIG.MIN_DAY_PERCENT
    ) {
      return {
        valid: false,
        reason: "allowance 样本太小",
      };
    }

    const turns =
      getModelTurns(countRow);

    const priced = [];
    const unknown = [];

    for (const m of modelRows) {
      const rate =
        getRate(m.model, m.speed);

      if (!rate) {
        unknown.push(m);
        continue;
      }

      // 当前一天的综合费率。
      // USD / 1M “当天平均结构 Token”
      const compositeRate =
        fu * rate.input +
        fc * rate.cached +
        fo * rate.output;

      if (!(compositeRate > 0)) {
        continue;
      }

      priced.push({
        ...m,
        rate,
        compositeRate,

        // 关键:
        //
        // quota share ≈ token * price
        //
        // 所以:
        //
        // token weight ≈ quota share / price
        tokenWeight:
          m.percent /
          compositeRate,
      });
    }

    if (!priced.length) {
      return {
        valid: false,
        reason: "当天模型价格均未知",
      };
    }

    const knownPct =
      priced.reduce(
        (sum, m) => sum + m.percent,
        0
      );

    // 为了避免未知模型偷偷导致金额偏低,
    // 只有所有 allowance 都能定价时
    // 才允许用于周额度反推。
    const coverage =
      knownPct / totalPercent;

    if (coverage < 0.999) {
      return {
        valid: false,
        reason:
          `价格覆盖仅 ${(coverage * 100).toFixed(2)}%`,
        unknown,
      };
    }

    const weightTotal =
      priced.reduce(
        (sum, m) =>
          sum + m.tokenWeight,
        0
      );

    if (!(weightTotal > 0)) {
      return {
        valid: false,
        reason: "无法计算 Token 权重",
      };
    }

    let dayUsd = 0;

    const allocatedModels =
      priced.map((m) => {
        const share =
          m.tokenWeight /
          weightTotal;

        // 假设各模型当天 uncached/cache/output
        // 结构与当天总结构一致。
        //
        // 个人接口没有更细的数据,
        // 这是必要的估算假设。
        const mu =
          uncached * share;

        const mc =
          cached * share;

        const mo =
          output * share;

        const modelUsd =
          (mu / 1e6) *
            m.rate.input +
          (mc / 1e6) *
            m.rate.cached +
          (mo / 1e6) *
            m.rate.output;

        dayUsd += modelUsd;

        return {
          model: m.model,
          speed: m.speed,

          allowancePercent:
            m.percent,

          turns:
            turns.get(m.model) || 0,

          tokenShare:
            share,

          uncached:
            mu,

          cached:
            mc,

          output:
            mo,

          totalTokens:
            mu + mc + mo,

          compositeRate:
            m.compositeRate,

          usd:
            modelUsd,
        };
      });

    // ==========================================================
    // 一整份 allowance 的等价美元价值
    // ==========================================================

    const allowanceUsd =
      dayUsd /
      (totalPercent / 100);

    return {
      valid: true,

      date:
        countRow.date,

      totalPercent,

      uncached,
      cached,
      output,
      totalTokens:
        total,

      dayUsd,

      allowanceUsd,

      models:
        allocatedModels,

      estimatedAllocation:
        allocatedModels.length > 1,

      unknown,
    };
  }

  // ============================================================
  // 合并 daily 两个接口
  // ============================================================

  function buildDays(counts, percents) {
    const countMap =
      new Map(
        (counts?.data || []).map(
          (x) => [x.date, x]
        )
      );

    const pctMap =
      new Map(
        (percents?.data || []).map(
          (x) => [x.date, x]
        )
      );

    const dates = [
      ...new Set([
        ...countMap.keys(),
        ...pctMap.keys(),
      ]),
    ].sort();

    return dates.map((date) => {
      const count =
        countMap.get(date) || null;

      const pct =
        pctMap.get(date) || null;

      const result =
        estimateDay(count, pct);

      return {
        date,
        count,
        pct,
        ...result,
      };
    });
  }

  // ============================================================
  // 当前周期额度聚合
  // ============================================================

  function estimateCycle(rows) {
    const usable =
      rows.filter(
        (r) =>
          r.valid &&
          Number.isFinite(r.allowanceUsd) &&
          r.allowanceUsd > 0
      );

    if (!usable.length) {
      return null;
    }

    if (usable.length === 1) {
      return {
        value:
          usable[0].allowanceUsd,

        samples: 1,
        spread: 0,

        rows: usable,
      };
    }

    const initial =
      median(
        usable.map(
          (r) => r.allowanceUsd
        )
      );

    const cluster =
      usable.filter(
        (r) =>
          Math.abs(
            r.allowanceUsd -
              initial
          ) /
            initial <=
          CONFIG.CLUSTER_TOLERANCE
      );

    if (!cluster.length) {
      return null;
    }

    // 按当天消耗百分比加权。
    // 额度消耗越大的日子统计误差通常越小。
    const weight =
      cluster.reduce(
        (sum, r) =>
          sum + r.totalPercent,
        0
      );

    const weighted =
      cluster.reduce(
        (sum, r) =>
          sum +
          r.allowanceUsd *
            r.totalPercent,
        0
      ) / weight;

    const values =
      cluster.map(
        (r) => r.allowanceUsd
      );

    const spread =
      values.length > 1
        ? (
            Math.max(...values) -
            Math.min(...values)
          ) /
          weighted
        : 0;

    return {
      value: weighted,

      samples:
        cluster.length,

      spread,

      dropped:
        usable.length -
        cluster.length,

      rows:
        cluster,
    };
  }

  // ============================================================
  // UI
  // ============================================================

  function addStyle() {
    document
      .getElementById(
        "cqv23-style"
      )
      ?.remove();

    const style =
      document.createElement("style");

    style.id = "cqv23-style";

    style.textContent = `
      #cqv23 {
        position: fixed;
        top: 2vh;
        left: 50%;
        transform: translateX(-50%);

        width: min(1180px, calc(100vw - 24px));
        max-height: 96vh;
        overflow: auto;

        z-index: 2147483647;

        background: white;
        color: #222;

        border: 1px solid #ddd;
        border-radius: 14px;

        padding: 20px;

        box-shadow:
          0 18px 70px rgba(0,0,0,.28);

        font-family:
          -apple-system,
          BlinkMacSystemFont,
          "Segoe UI",
          sans-serif;
      }

      #cqv23 * {
        box-sizing: border-box;
      }

      #cqv23 .head {
        display: flex;
        justify-content: space-between;
        align-items: center;

        margin-bottom: 14px;
      }

      #cqv23 .title {
        font-size: 19px;
        font-weight: 700;
      }

      #cqv23 .close {
        font-size: 28px;
        cursor: pointer;
        color: #888;
      }

      #cqv23 .cards {
        display: grid;
        grid-template-columns: repeat(6, 1fr);
        gap: 10px;
        margin-bottom: 14px;
      }

      #cqv23 .card {
        border: 1px solid #ececec;
        background: #f7f7f7;

        border-radius: 9px;
        padding: 11px;
      }

      #cqv23 .card.good {
        background: #eefaf5;
        border-color: #cfeee1;
      }

      #cqv23 .card.used {
        background: #fff8ea;
        border-color: #f4dfad;
      }

      #cqv23 .label {
        color: #777;
        font-size: 11px;
        margin-bottom: 5px;
      }

      #cqv23 .value {
        font-size: 16px;
        font-weight: 750;
        color: #0f8f70;
        white-space: nowrap;
      }

      #cqv23 .card.used .value {
        color: #a35d00;
      }

      #cqv23 .sub {
        color: #777;
        font-size: 10px;
        margin-top: 4px;
      }

      #cqv23 .note {
        background: #fafafa;
        border: 1px solid #eee;
        border-radius: 8px;

        padding: 10px 12px;
        margin-bottom: 14px;

        font-size: 12px;
        line-height: 1.6;
        color: #555;
      }

      #cqv23 .tablebox {
        max-height: 390px;
        overflow: auto;

        border: 1px solid #eee;
        border-radius: 8px;

        margin-bottom: 14px;
      }

      #cqv23 table {
        width: 100%;
        border-collapse: collapse;
        font-size: 11px;
      }

      #cqv23 th,
      #cqv23 td {
        padding: 7px 6px;
        border-bottom: 1px solid #eee;
        text-align: left;
        white-space: nowrap;
      }

      #cqv23 th {
        position: sticky;
        top: 0;
        z-index: 1;

        background: #f5f5f5;
        color: #666;
      }

      #cqv23 .goodtext {
        color: #0f8f70;
        font-weight: 600;
      }

      #cqv23 .warn {
        color: #a35d00;
      }

      #cqv23 .muted {
        color: #999;
      }

      #cqv23 details {
        margin-top: 5px;
      }

      #cqv23 summary {
        cursor: pointer;
      }

      @media(max-width: 900px) {
        #cqv23 .cards {
          grid-template-columns: repeat(3,1fr);
        }
      }
    `;

    document.head.appendChild(style);
  }

  function render({
    usage,
    rows,
    cycleStartDate,
    estimate,
  }) {
    document
      .getElementById("cqv23")
      ?.remove();

    addStyle();

    const root =
      document.createElement("div");

    root.id = "cqv23";

    const window =
      usage?.rate_limit
        ?.secondary_window ||
      usage?.rate_limit
        ?.primary_window;

    const livePercent =
      clamp(
        n(window?.used_percent),
        0,
        100
      );

    const weeklyUsd =
      estimate?.value ?? null;

    const usedUsd =
      weeklyUsd != null
        ? weeklyUsd *
          livePercent /
          100
        : null;

    const leftUsd =
      weeklyUsd != null
        ? weeklyUsd - usedUsd
        : null;

    const weeklyCredits =
      weeklyUsd != null
        ? weeklyUsd /
          CONFIG.USD_PER_CREDIT
        : null;

    const usedCredits =
      usedUsd != null
        ? usedUsd /
          CONFIG.USD_PER_CREDIT
        : null;

    const currentRows =
      rows.filter(
        (r) =>
          r.date >= cycleStartDate
      );

    const settledPct =
      currentRows.reduce(
        (sum, r) =>
          sum + n(r.totalPercent),
        0
      );

    const rowsHtml =
      [...rows]
        .sort(
          (a, b) =>
            b.date.localeCompare(a.date)
        )
        .map((r) => {
          const current =
            r.date >= cycleStartDate;

          if (!r.valid) {
            return `
              <tr>
                <td>${current ? "● " : ""}${r.date}</td>
                <td>${fmtPct(
                  (r.pct?.models || [])
                    .reduce(
                      (s,m) => s+n(m.credits),
                      0
                    )
                )}</td>
                <td colspan="4" class="muted">
                  ${r.reason || "无可用数据"}
                </td>
              </tr>
            `;
          }

          const modelText =
            r.models
              .map(
                (m) =>
                  `${m.model.replace("gpt-","")} ` +
                  `${fmtPct(m.allowancePercent)}`
              )
              .join(" / ");

          return `
            <tr>
              <td>
                ${current ? "● " : ""}
                ${r.date}
              </td>

              <td>
                ${fmtPct(r.totalPercent)}
              </td>

              <td>
                ${modelText}
              </td>

              <td>
                ${fmtNum(r.totalTokens)}
              </td>

              <td>
                ${fmtMoney(r.dayUsd)}
              </td>

              <td class="goodtext">
                ${fmtMoney(r.allowanceUsd)}
                ${
                  r.estimatedAllocation
                    ? " ≈"
                    : ""
                }
              </td>
            </tr>
          `;
        })
        .join("");

    const modelDetails =
      currentRows
        .filter((r) => r.valid)
        .flatMap((r) =>
          r.models.map((m) => ({
            date: r.date,
            ...m,
          }))
        )
        .map(
          (m) => `
            <tr>
              <td>${m.date}</td>
              <td>${m.model.replace("gpt-","")}</td>
              <td>${m.turns}</td>
              <td>${fmtPct(m.allowancePercent)}</td>
              <td>${(m.tokenShare * 100).toFixed(2)}%</td>
              <td>${fmtNum(m.uncached)}</td>
              <td>${fmtNum(m.cached)}</td>
              <td>${fmtNum(m.output)}</td>
              <td>${fmtNum(m.totalTokens)}</td>
              <td>${fmtMoney(m.usd)}</td>
            </tr>
          `
        )
        .join("");

    root.innerHTML = `
      <div class="head">
        <div class="title">
          📊 Codex Quota Compass V2.3 · 多模型费率反校正
        </div>

        <div
          class="close"
          id="cqv23-close"
        >
          ×
        </div>
      </div>

      <div class="cards">

        <div class="card">
          <div class="label">
            当前计划
          </div>

          <div class="value">
            ${usage?.plan_type || "unknown"}
          </div>

          <div class="sub">
            仅展示,不参与任何额度计算
          </div>
        </div>

        <div class="card">
          <div class="label">
            实时周额度已用
          </div>

          <div class="value">
            ${livePercent.toFixed(1)}%
          </div>

          <div class="sub">
            每日接口已结算 ${settledPct.toFixed(3)}%
          </div>
        </div>

        <div class="card good">
          <div class="label">
            当前完整周额度
          </div>

          <div class="value">
            ${
              weeklyUsd != null
                ? fmtMoney(weeklyUsd)
                : "等待当天 Token"
            }
          </div>

          <div class="sub">
            ${
              estimate
                ? `${estimate.samples} 个当前周期真实日样本`
                : "无套餐倍率 / 无历史兜底"
            }
          </div>
        </div>

        <div class="card used">
          <div class="label">
            本周已用等价价值
          </div>

          <div class="value">
            ${
              usedUsd != null
                ? fmtMoney(usedUsd)
                : "—"
            }
          </div>

          <div class="sub">
            ${
              usedCredits != null
                ? `${fmtNum(usedCredits)} Credits eq.`
                : "—"
            }
          </div>
        </div>

        <div class="card">
          <div class="label">
            本周剩余等价价值
          </div>

          <div class="value">
            ${
              leftUsd != null
                ? fmtMoney(leftUsd)
                : "—"
            }
          </div>
        </div>

        <div class="card">
          <div class="label">
            周总等价 Credits
          </div>

          <div class="value">
            ${
              weeklyCredits != null
                ? fmtNum(weeklyCredits)
                : "—"
            }
          </div>

          <div class="sub">
            $0.04 / Credit 仅作换算展示
          </div>
        </div>

      </div>

      <div class="note">
        <b>算法:</b>
        多模型日期不会再被丢弃,也不会简单按调用次数或额度百分比直接分 Token。
        脚本先用当天真实 uncached / cached / output 结构计算每个模型的
        <b>综合 API 费率</b>,再按照
        <code>模型 allowance% ÷ 综合费率</code>
        反向估算模型 Token 权重。

        <br><br>

        每模型的 <b>turns 是接口真实调用轮数</b>,
        但不会拿 turns 直接计价,因为不同调用的 Token 长度差异可能极大。

        <br>

        混模日的 per-model Token 属于
        <b>费率校正估算</b>;
        当天只有一个模型时,三类 Token 则无需分配,属于直接值。

        <br>

        <b>长上下文加价已完全忽略。</b>
        无论单次请求是否超过 272K,本脚本都只使用基础 API Token rate card。
      </div>

      <div class="tablebox">
        <table>
          <thead>
            <tr>
              <th>日期</th>
              <th>Allowance%</th>
              <th>模型占比</th>
              <th>总 Tokens</th>
              <th>当日等价$</th>
              <th>反推完整周$</th>
            </tr>
          </thead>

          <tbody>
            ${rowsHtml}
          </tbody>
        </table>
      </div>

      <details>
        <summary>
          🤖 查看当前周期各模型估算用量
        </summary>

        <div class="tablebox" style="margin-top:8px">
          <table>
            <thead>
              <tr>
                <th>日期</th>
                <th>模型</th>
                <th>实际 Turns</th>
                <th>Allowance%</th>
                <th>估算 Token 占比</th>
                <th>未缓存输入</th>
                <th>缓存输入</th>
                <th>输出</th>
                <th>总 Token</th>
                <th>等价$</th>
              </tr>
            </thead>

            <tbody>
              ${
                modelDetails ||
                `<tr>
                  <td colspan="10" class="muted">
                    当前周期 Token 尚未结算
                  </td>
                </tr>`
              }
            </tbody>
          </table>
        </div>
      </details>

      <div class="note" style="margin-top:14px;margin-bottom:0">
        当前周期开始:
        <b>${cycleStartDate}</b>

        ${
          estimate
            ? `
              <br>
              当前周期有效样本:
              <b>${estimate.samples}</b> 天;
              完整周额度:
              <b>${fmtMoney(estimate.value)}</b>;
              样本离散:
              <b>${(estimate.spread * 100).toFixed(2)}%</b>。
            `
            : `
              <br>
              当前周期目前没有同时拥有
              <b>日 Token + 日 allowance%</b>
              的完整样本,因此还无法确定分母。
              这不是套餐倍率问题,而是当天 Token 数据尚未落库。
            `
        }
      </div>
    `;

    document.body.appendChild(root);

    document
      .getElementById("cqv23-close")
      .onclick = () => {
        root.remove();
        document
          .getElementById("cqv23-style")
          ?.remove();
      };
  }

  // ============================================================
  // MAIN
  // ============================================================

  try {
    console.log(
      "[Quota Compass V2.3] 获取登录信息..."
    );

    const token =
      getAccessToken();

    if (!token) {
      throw new Error(
        "无法取得 ChatGPT access token"
      );
    }

    const usage =
      await apiGet(
        "/backend-api/wham/usage",
        token
      );

    const window =
      usage?.rate_limit
        ?.secondary_window ||
      usage?.rate_limit
        ?.primary_window;

    if (!window) {
      throw new Error(
        "没有找到 weekly rate limit window"
      );
    }

    const now = Date.now();

    const today =
      dayKey(now);

    const startDate =
      dayKey(
        now -
        CONFIG.HISTORY_DAYS *
        86400000
      );

    const endDate =
      addDays(today, 1);

    const range =
      `start_date=${encodeURIComponent(startDate)}` +
      `&end_date=${encodeURIComponent(endDate)}` +
      `&group_by=day`;

    const cycleStartMs =
      (
        n(window.reset_at) -
        n(window.limit_window_seconds)
      ) * 1000;

    const cycleStartDate =
      dayKey(cycleStartMs);

    console.log(
      "[Quota Compass V2.3] 当前周期:",
      cycleStartDate
    );

    const [
      counts,
      percents,
    ] = await Promise.all([
      apiGet(
        "/backend-api/wham/analytics/" +
        "daily-workspace-usage-counts?" +
        range +
        "&workspace_user=true",
        token
      ),

      apiGet(
        "/backend-api/wham/usage/" +
        "daily-token-usage-breakdown?" +
        range,
        token
      ),
    ]);

    const rows =
      buildDays(
        counts,
        percents
      );

    const currentRows =
      rows.filter(
        (r) =>
          r.date >= cycleStartDate
      );

    const estimate =
      estimateCycle(
        currentRows
      );

    console.table(
      rows
        .filter(
          (r) =>
            r.valid ||
            (
              r.pct?.models ||
              []
            ).some(
              (m) =>
                n(m.credits) > 0
            )
        )
        .map((r) => ({
          date:
            r.date,

          current:
            r.date >= cycleStartDate,

          allowance_pct:
            r.totalPercent || 0,

          tokens:
            r.totalTokens || 0,

          day_usd:
            r.dayUsd
              ? +r.dayUsd.toFixed(3)
              : null,

          full_week_usd:
            r.allowanceUsd
              ? +r.allowanceUsd.toFixed(2)
              : null,

          result:
            r.valid
              ? (
                  r.estimatedAllocation
                    ? "费率校正"
                    : "单模型直接值"
                )
              : r.reason,
        }))
    );

    if (estimate) {
      const live =
        clamp(
          n(window.used_percent),
          0,
          100
        );

      console.log(
        "====== 当前周实测 ======"
      );

      console.log(
        "周额度 API 等价价值:",
        estimate.value
      );

      console.log(
        "实时已用:",
        live + "%"
      );

      console.log(
        "本周已用等价价值:",
        estimate.value *
        live /
        100
      );

      console.log(
        "本周剩余等价价值:",
        estimate.value *
        (
          1 -
          live /
          100
        )
      );
    }

    render({
      usage,
      rows,
      cycleStartDate,
      estimate,
    });

    console.log(
      "[Quota Compass V2.3] ✅ 完成"
    );
  } catch (e) {
    console.error(
      "[Quota Compass V2.3]",
      e
    );

    alert(
      "Quota Compass V2.3 失败:\n\n" +
      (
        e?.message ||
        String(e)
      )
    );
  }
})();